fix: Chatwoot integration drops templateMessage, conversation never created - #2666
fix: Chatwoot integration drops templateMessage, conversation never created#2666pbarbagli wants to merge 4 commits into
Conversation
…reated getTypeMessage() has no case for templateMessage (WhatsApp Business template), so the first message from a contact using this type produces no body, eventWhatsapp logs 'no body message found' and returns before calling createConversation. The conversation only gets created later if a normal-type message follows. Extracts hydratedTemplate.hydratedContentText, mirroring how templateMessage is already handled in the getContentMessage() fallback a few lines below. Fixes the templateMessage item tracked in evolution-foundation#2014. Duplicate of evolution-foundation#2500.
Reviewer's guide (collapsed on small PRs)Reviewer's GuideAdds support for extracting WhatsApp Business template message text in Chatwoot integration so template-based inbound messages correctly create conversations instead of being dropped. Sequence diagram for handling WhatsApp templateMessage in Chatwoot integrationsequenceDiagram
participant WhatsApp as WhatsAppPlatform
participant ChatwootService
participant ChatwootAPI as ChatwootBackend
WhatsApp->>ChatwootService: eventWhatsapp(msg)
ChatwootService->>ChatwootService: getTypeMessage(msg)
note right of ChatwootService: Extract templateMessage.hydratedTemplate.hydratedContentText
ChatwootService->>ChatwootService: getContentMessage(types)
alt body message found
ChatwootService->>ChatwootAPI: createConversation(body, incoming)
else no body message found
ChatwootService->>ChatwootService: eventWhatsapp logs no_body_message_found
end
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts" line_range="1781" />
<code_context>
msg?.message?.viewOnceMessageV2?.message?.imageMessage?.url ||
msg?.message?.viewOnceMessageV2?.message?.videoMessage?.url ||
msg?.message?.viewOnceMessageV2?.message?.audioMessage?.url,
+ templateMessage: msg.templateMessage?.hydratedTemplate?.hydratedContentText,
};
</code_context>
<issue_to_address>
**issue (bug_risk):** Align null-safety with surrounding fields by adding optional chaining on `msg`.
`templateMessage` is the only field that doesn’t use optional chaining on `msg`. If `msg` is ever `undefined`/`null`, `msg.templateMessage…` will throw. Please update this access to `msg?.templateMessage?.hydratedTemplate?.hydratedContentText` to match the surrounding null-safety.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Matches the null-safety style of the surrounding fields in getTypeMessage() — msg could theoretically be undefined/null. Addresses sourcery-ai review comment on evolution-foundation#2666.
Production data confirming this — and evidence the same root cause affects more types than
|
| message type | inbound in Evolution | absent from Chatwoot | drop rate |
|---|---|---|---|
templateMessage |
37 | 37 | 100% |
secretEncryptedMessage |
24 | 24 | 100% |
associatedChildMessage |
13 | 13 | 100% |
buttonsMessage |
12 | 12 | 100% |
albumMessage |
11 | 11 | 100% |
interactiveMessage |
7 | 7 | 100% |
placeholderMessage |
3 | 3 | 100% |
templateMessage is 37 for 37 — not a single one produced a conversation, which matches the "no body message found" early-return described here exactly.
The rest of the table is what I think is worth adding to the discussion: every type without a case in getTypeMessage() drops at 100%. So the one-line fix here is correct but treats one symptom of a shared shape — a type the extractor doesn't know silently ends the ingestion path instead of falling through.
Two suggestions, offered as a user rather than a maintainer:
- Consider falling back to
getContentMessage()(or a generic placeholder) for any unrecognised type, so an unknown type degrades to a conversation with poor content rather than no conversation at all. - Consider logging
"no body message found"atwarn/errorwith the message type included. Right now this failure is completely silent from the operator's side: no error surfaces, no queue grows, nothing retries. We ran for weeks without knowing, and the only way we detected it was cross-checking two databases by hand.
Happy to provide more detail from our dataset if useful.
…WhatsApp message types getTypeMessage() only recognizes a fixed list of message types; anything else produces no body, logs a bare "no body message found", and the conversation is silently never created. A user reported this on PR evolution-foundation#2666 with production data (60-day window, 1:1 inbound): every unrecognized type dropped at 100% — templateMessage (37/37), secretEncryptedMessage (24/24), associatedChildMessage (13/13), buttonsMessage (12/12), albumMessage (11/11), interactiveMessage (7/7), placeholderMessage (3/3). Confirmed the same drop happening on this instance (a live secretEncryptedMessage silently dropped a few hours after redeploy). Looked up each type's actual shape in Baileys' WAProto.proto before deciding how to handle it, rather than a blanket fallback: - buttonsMessage: real content field (contentText) was never extracted - interactiveMessage: real content field (body.text) was never extracted - albumMessage: carries no text by design (WAProto: only expectedImageCount/expectedVideoCount) — synthesize an informative label instead of a text extractor that could never exist - associatedChildMessage: WAProto FutureProofMessage, just wraps a real embedded message (e.g. an album's individual photo) — unwrap recursively in getConversationMessage() instead of treating it as its own type - placeholderMessage: WhatsApp-internal only (MASK_LINKED_DEVICES) — never real user content, so explicitly skip rather than warn/drop - secretEncryptedMessage: encrypted edit/event payload targeting an existing message (SecretEncType: EVENT_EDIT/MESSAGE_EDIT), not new displayable content — decrypting and applying it as an edit isn't implemented, so explicitly skip with a clear reason instead of logging a misleading "no body" warning for what isn't a lost message For any other/future type not covered above, degrade instead of dropping: create the conversation with a "[mensagem não suportada: X]" placeholder body and log the actual type name, so a gap like this is visible from the logs instead of only discoverable by reconciling two databases by hand (as the reporting user had to do).
…teMessage Two more common person-to-person message types with no content extractor in getTypeMessage(), so they'd have fallen into the new generic placeholder instead of showing real content: poll questions (pollCreationMessage.name) and group invite links (groupInviteMessage .caption, falling back to .groupName).
Thanks @abckx-tech for the production data — really useful. We hit the same thing on our own instance (a secretEncryptedMessage got silently dropped a few hours after we noticed this thread), so I went ahead and broadened this PR to cover the full pattern instead of just templateMessage. |
getTypeMessage()doesn't have a case fortemplateMessage, so when acontact's first message arrives as a WhatsApp Business template (common
for B2B outreach sent via WhatsApp Business Platform), no content is
extracted,
eventWhatsapplogs"no body message found"and returnsbefore ever calling
createConversation— the conversation is silentlydropped, and only gets created later if/when a normal-type message
follows (e.g. a reply).
This adds
hydratedTemplate.hydratedContentTextextraction, mirroringhow
templateMessageis already handled in the othergetContentMessage()fallback path a few lines below, which just isn'tused on this ingestion path.
Fixes the
templateMessageitem tracked in #2014.Duplicate of the (closed) #2500.
Tested against a real production instance: reproduced the exact
"no body message found"log with a live inboundtemplateMessage,applied this one-line fix, confirmed the conversation now gets created
correctly (as
incoming) instead of being dropped.Summary by Sourcery
Bug Fixes: